#include "icemanager.h"

void *runGlobalMainloop( void* arg )
{
    GMainLoop *global_mainloop = (GMainLoop *)arg;
    g_main_loop_run( global_mainloop );
    pthread_exit( NULL );
}

ICEManager::ICEManager()
{
    agent = NULL;
    global_mainloop = NULL;
    stun_server = NULL;
    turn_server = NULL;
}

ICEManager::~ICEManager()
{
    if( agent )
        g_object_unref( agent );

    if( global_mainloop )
        g_main_loop_unref( global_mainloop );

    while( !streams.empty() )
    {
        delete streams.back();
        streams.pop_back();
    }
}

void ICEManager::initialise( ICEServer* stun_server, ICEServer* turn_server )
{
    // Save a reference to the servers
    this->stun_server = stun_server;
    this->turn_server = turn_server;

    // Initialise glib
    g_type_init();
    if( !g_thread_supported() )
        g_thread_init( NULL );
    global_mainloop = g_main_loop_new( NULL, FALSE );

    // Create a nice agent
    std::cout << "Criando o agente nice." << std::endl;
    agent = nice_agent_new( g_main_loop_get_context( global_mainloop ), NICE_COMPATIBILITY_DRAFT19 );

    // Connect the signals
    std::cout << "Conectando os sinais." << std::endl;
    g_signal_connect( G_OBJECT( agent ), "candidate-gathering-done", G_CALLBACK( cb_candidate_gathering_done ), (gpointer)this );
    g_signal_connect( G_OBJECT( agent ), "new-selected-pair", G_CALLBACK( cb_new_selected_pair ), (gpointer)this );

    // Setting stun server properties
    if( stun_server )
    {
        std::cout << "Estabelecendo as propriedades do servidor STUN." << std::endl;
        g_object_set( G_OBJECT( agent ), "stun-server", stun_server->getIp().c_str(),  NULL );
        g_object_set( G_OBJECT( agent ), "stun-server-port", stun_server->getPort(),  NULL );
    }

    // Start the global_mainloop in a new thread
    pthread_create( &global_mainloop_thread, NULL, runGlobalMainloop, (void *)global_mainloop );
}

void ICEManager::release()
{
    g_main_loop_quit( global_mainloop );
}

ICEStream* ICEManager::createStream( guint n_component )
{
    // Create a new stream
    std::cout << "Criando 'stream'..." << std::endl;
    guint stream_id = nice_agent_add_stream( agent, n_component );
    ICEStream* stream = new ICEStream( agent, stream_id, n_component );
    std::cout << "'Stream' de ID = " << stream_id << " criado!" << std::endl;
    streams.push_back( stream );

    // Attach to the component to receive the data
    std::cout << "Anexando o receptor de dados." << std::endl;
    for( guint component_id = 1; component_id <= n_component; component_id++ )
        nice_agent_attach_recv( agent, stream_id, component_id, g_main_loop_get_context( global_mainloop ), cb_nice_recv, (gpointer)stream );

    // Setting turn server properties
    if( turn_server )
    {
        std::cout << "Estabelecendo as propriedades do servidor TURN." << std::endl;
        for( guint component_id = 1; component_id <= n_component; component_id++ )
        {
            nice_agent_set_relay_info( agent, stream_id, component_id, turn_server->getIp().c_str(), turn_server->getPort(),
                                       turn_server->getUsername().c_str(), turn_server->getPassword().c_str(), NICE_RELAY_TYPE_TURN_UDP);
        }
    }

    return stream;
}

void ICEManager::cb_candidate_gathering_done( NiceAgent* agent, guint stream_id, gpointer user_data )
{
    // For the compiler
    (void)agent;

    ICEManager* icemgr = (ICEManager*)user_data;
    icemgr->streams[stream_id-1]->candidateGatheringDone();
}

void ICEManager::cb_new_selected_pair( NiceAgent* agent, guint stream_id, guint component_id,
                                       gchar* lfoundation, gchar* rfoundation, gpointer user_data )
{
    // For the compiler
    (void)agent; (void)component_id; (void)lfoundation; (void)rfoundation;

    ICEManager* icemgr = (ICEManager*)user_data;
    icemgr->streams[stream_id-1]->newSelectedPair();
}

void ICEManager::cb_nice_recv( NiceAgent* agent, guint stream_id, guint component_id, guint len, gchar* buf, gpointer user_data )
{
    // For the compiler
    (void)agent; (void)stream_id;

    ICEStream* stream = (ICEStream*)user_data;
    stream->receive( len, buf, component_id );
}
